package com.lytoyo.neihanduanzi.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggest;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import com.lytoyo.neihanduanzi.model.index.BlogIndex;
import com.lytoyo.neihanduanzi.model.index.SuggestIndex;
import com.lytoyo.neihanduanzi.model.index.UserIndex;
import com.lytoyo.neihanduanzi.model.query.BlogIndexQuery;
import com.lytoyo.neihanduanzi.model.query.UserIndexQuery;
import com.lytoyo.neihanduanzi.model.vo.BlogIndexVo;
import com.lytoyo.neihanduanzi.model.vo.BlogSearchResponseVo;
import com.lytoyo.neihanduanzi.model.vo.UserIndexVo;
import com.lytoyo.neihanduanzi.model.vo.UserSearchResponseVo;
import com.lytoyo.neihanduanzi.service.SearchService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Package:com.lytoyo.neihanduanzi.service.impl
 *
 * @ClassName:SearchServiceImpl
 * @Create:2025/4/10 16:05
 **/
@Service
@Slf4j
public class SearchServiceImpl implements SearchService {
    @Resource
    private ElasticsearchClient elasticsearchClient;

    // 创建高亮标签模板
    private static final String HIGHLIGHT_PRE = "<span style='color:#ff4083'>";
    private static final String HIGHLIGHT_POST = "</span>";
    /**
     * 用户查询
     *
     * @param userIndexQuery
     * @return
     */
    @Override
    public UserSearchResponseVo search(UserIndexQuery userIndexQuery) {

        SearchRequest request = this.buildUserQueryDsl(userIndexQuery);
        SearchResponse<UserIndex> response = null;
        try {
            response = elasticsearchClient.search(request, UserIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //得到返回的结果集
        UserSearchResponseVo responseVo = this.parseUserSearchResult(response);
        responseVo.setPageSize(userIndexQuery.getPageSize());
        responseVo.setPageNo(userIndexQuery.getPageNo());
        //获取总页数
        long totalPages = (responseVo.getTotal() + userIndexQuery.getPageSize() - 1) / userIndexQuery.getPageSize();
        responseVo.setTotalPages(totalPages);
        return responseVo;
    }

    /**
     * 关键词补全
     *
     * @param keyword
     * @return
     */
    @Override
    public List<String> completeSuggest(String keyword) {

        //dsl语句
        SearchRequest.Builder searchRequest = new SearchRequest.Builder();
        searchRequest.index("suggest").suggest(
                s -> s.suggesters("suggestionKeyword", f -> f.prefix(keyword).completion(
                                c -> c.field("keyword").skipDuplicates(true).size(10).fuzzy(z -> z.fuzziness("auto"))
                        ))
                        .suggesters("suggestionkeywordPinyin", f -> f.prefix(keyword).completion(
                                c -> c.field("keywordPinyin").skipDuplicates(true).size(10).fuzzy(z -> z.fuzziness("auto"))
                        ))
                        .suggesters("suggestionkeywordSequence", f -> f.prefix(keyword).completion(
                                c -> c.field("keywordSequence").skipDuplicates(true).size(10).fuzzy(z -> z.fuzziness("auto"))
                        ))
        );
        //获取查询结果
        SearchResponse<SuggestIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(searchRequest.build(), SuggestIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //获取到结果集，数据转换,集合内容不可重复
        HashSet<String> titleSet = new HashSet<>();
        titleSet.addAll(this.parseSearchResult(searchResponse, "suggestionKeyword"));
        titleSet.addAll(this.parseSearchResult(searchResponse, "suggestionKeywordPinyin"));
        titleSet.addAll(this.parseSearchResult(searchResponse, "suggestionKeywordSequence"));

        //判断
        if (titleSet.size() < 10) {
            //使用查询数据的方式来填充集合数据，让这个提示信息够10条数据
            SearchResponse<SuggestIndex> response = null;
            try {
                response = elasticsearchClient.search(s -> s.index("suggest")
                                .query(f -> f.match(m -> m.field("title")
                                        .query(keyword))),
                        SuggestIndex.class);
            } catch (IOException e) {
                throw new RuntimeException();
            }
            //从查询结果集中获取数据
            for (Hit<SuggestIndex> hit : response.hits().hits()) {
                //获取数据结果
                SuggestIndex suggestIndex = hit.source();
                titleSet.add(suggestIndex.getTitle());
                //判断当前这个集合的长度
                if (titleSet.size() == 10) break;
            }
        }
        return new ArrayList<>(titleSet);
    }

    /**
     * 综合查询
     * @param keyword
     * @return
     */
    @Override
    public Map<String, Object> comprehensiveSearch(String keyword, Integer pageNo, Integer pageSize) {
        SearchResponse<UserIndex> userSearchResponse = null;
        SearchResponse< BlogIndex> blogSearchResponse = null;
        //如果是第一次查询，需要查询一条用户信息
        if (pageNo < 2) {
            UserIndexQuery userIndexQuery = new UserIndexQuery()
                                                .setKeyword(keyword)
                                                .setOrder(null)
                                                .setPageNo(1)
                                                .setPageSize(1);
            SearchRequest userSearchRequest = buildUserQueryDsl(userIndexQuery);
            try {
                userSearchResponse = elasticsearchClient.search(userSearchRequest, UserIndex.class);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //查询到的用户结果集
        UserSearchResponseVo userResponseVo = parseUserSearchResult(userSearchResponse);
        if(null!=userResponseVo){
            userResponseVo.setTotal((long) userResponseVo.getList().size());
        }
        BlogIndexQuery blogIndexQuery = new BlogIndexQuery()
                                            .setKeyword(keyword)
                                            .setOrder(null)
                                            .setPageNo(pageNo)
                                            .setPageSize(pageSize);
        SearchRequest blogSearchRequest = buildBlogQueryDsl(blogIndexQuery);
        try{
            blogSearchResponse = elasticsearchClient.search(blogSearchRequest, BlogIndex.class);
        }catch (IOException e){
            e.printStackTrace();
        }
        //查询到的博客结果集
        BlogSearchResponseVo blogResponseVo = this.parseBlogSearchResult(blogSearchResponse);
        blogResponseVo.setTotal((long) blogResponseVo.getList().size());
        HashMap<String, Object> map = new HashMap<>();
        map.put("user",userResponseVo);
        map.put("blog",blogResponseVo);
        map.put("pageNo",pageNo);
        map.put("pageSize",pageNo > 1?pageSize:pageSize+1);
        map.put("total",userResponseVo == null ? blogResponseVo.getTotal() : blogResponseVo.getTotal() + userResponseVo.getTotal());
        return map;
    }

    /**
     * 构建blog的dsl语句
     *
     * @param blogIndexQuery
     * @return
     */
    private SearchRequest buildBlogQueryDsl(BlogIndexQuery blogIndexQuery) {
        String keyword = blogIndexQuery.getKeyword();
        SearchRequest.Builder requestBuilder = new SearchRequest.Builder();
        //bool条件
        BoolQuery.Builder boolQuery = new BoolQuery.Builder();
        if (!StringUtils.isEmpty(keyword)) {
            //关键词
            boolQuery.should(f -> f.match(s -> s.field("title").query(keyword)));
            //高亮
            requestBuilder.highlight(h -> h.fields("title", f -> f.preTags(HIGHLIGHT_PRE).postTags(HIGHLIGHT_POST)));
        }
        requestBuilder.sort(f -> f.field(o -> o.field("liked").order(SortOrder.Desc)));
        //分页
        int pageNo = blogIndexQuery.getPageNo() <= 0 ? 1 : blogIndexQuery.getPageNo();
        int pageSize = blogIndexQuery.getPageSize() <= 0 ? 1 : blogIndexQuery.getPageSize();
        int from = (pageNo - 1) * pageSize;
        requestBuilder.from(from);
        requestBuilder.size(pageSize);
        //设置索引
        requestBuilder.index("blog").query(f -> f.bool(boolQuery.build()));
        SearchRequest searchRequest = requestBuilder.build();
        return searchRequest;
    }

    /**
     * 构建user的dsl语句
     *
     * @param userIndexQuery
     * @return
     */
    private SearchRequest buildUserQueryDsl(UserIndexQuery userIndexQuery) {
        //检索入口：关键词
        String keyword = userIndexQuery.getKeyword();
        SearchRequest.Builder requestBuilder = new SearchRequest.Builder();
        //bool条件
        BoolQuery.Builder boolQuery = new BoolQuery.Builder();
        if (!StringUtils.isEmpty(keyword)) {
            //检索昵称
            boolQuery.should(f -> f.match(s -> s.field("nickname").query(keyword)));
            //高亮
            requestBuilder.highlight(h -> h.fields("nickname", f -> f.preTags(HIGHLIGHT_PRE).postTags(HIGHLIGHT_POST)));
        }
        //排序
        requestBuilder.sort(f -> f.field(o -> o.field("fans").order(SortOrder.Desc)));
        List<String> list = Arrays.asList("gender", "email", "signature");
        //排除某些字段
        requestBuilder.source(s -> s.filter(f -> f.excludes(list)));
        //分页
        int pageNo = userIndexQuery.getPageNo() <= 0 ? 1 : userIndexQuery.getPageNo();
        int pageSize = userIndexQuery.getPageSize() <= 0 ? 10 : userIndexQuery.getPageSize();
        int from = (pageNo - 1) * pageSize;
        requestBuilder.from(from);
        requestBuilder.size(pageSize);
        //设置索引
        requestBuilder.index("user").query(f -> f.bool(boolQuery.build()));
        //创建对象
        SearchRequest searchRequest = requestBuilder.build();
        return searchRequest;
    }

    /**
     * 处理聚合结果集
     *
     * @param response
     * @param suggestName
     * @return
     */
    private List<String> parseSearchResult(SearchResponse<SuggestIndex> response, String suggestName) {
        //创建集合
        ArrayList<String> suggestList = new ArrayList<>();
        Map<String, List<Suggestion<SuggestIndex>>> groupBySuggestionListAggMap = response.suggest();
        if (groupBySuggestionListAggMap.get(suggestName) == null) return suggestList;
        groupBySuggestionListAggMap.get(suggestName).forEach(item -> {
            CompletionSuggest<SuggestIndex> completionSuggest = item.completion();
            completionSuggest.options().forEach(it -> {
                SuggestIndex suggestIndex = it.source();
                suggestList.add(suggestIndex.getTitle());
            });
        });
        return suggestList;
    }

    /**
     * 获取结果集对象
     *
     * @param searchResponse
     * @return
     */
    private UserSearchResponseVo parseUserSearchResult(SearchResponse<UserIndex> searchResponse) {
        UserSearchResponseVo userSearchResponseVo = new UserSearchResponseVo();
        if(searchResponse == null){
            return userSearchResponseVo.setTotal(0l).setList(new ArrayList<>());
        }
        //获取数据
        HitsMetadata<UserIndex> hits = searchResponse.hits();

        //总记录数
        userSearchResponseVo.setTotal(hits.total().value());
        List<Hit<UserIndex>> subHits = hits.hits();
        //判断
        if (!CollectionUtils.isEmpty(subHits)) {
            List<UserIndexVo> list = subHits.stream().map(userIndexHit -> {
                UserIndexVo userIndexVo = new UserIndexVo();
                UserIndex userIndex = userIndexHit.source();
                log.info("userIndex:{}", userIndex);
                BeanUtils.copyProperties(userIndex, userIndexVo, UserIndexVo.class);
                //判断用户是否根据关键词检索
                if (null != userIndexHit.highlight().get("nickname")) {
                    //获取高亮数据
                    String nickname = userIndexHit.highlight().get("nickname").get(0);
                    //赋值高亮数据
                    userIndexVo.setNickname(nickname);
                }
                //返回数据
                return userIndexVo;
            }).collect(Collectors.toList());
            //赋值
            userSearchResponseVo.setList(list);
        }
        return userSearchResponseVo;
    }
    private BlogSearchResponseVo parseBlogSearchResult(SearchResponse<BlogIndex> searchResponse){
        BlogSearchResponseVo blogSearchResponseVo = new BlogSearchResponseVo();
        //获取数据
        HitsMetadata<BlogIndex> hits = searchResponse.hits();

        //总记录数
        blogSearchResponseVo.setTotal(hits.total().value());
        List<Hit<BlogIndex>> subHits = hits.hits();
        //判断
        if(!CollectionUtils.isEmpty(subHits)){
            List<BlogIndexVo> list = subHits.stream().map(blogIndexHit -> {
                BlogIndexVo blogIndexVo = new BlogIndexVo();
                BlogIndex blogIndex = blogIndexHit.source();
                BeanUtils.copyProperties(blogIndex, blogIndexVo, BlogIndexVo.class);
                //判断用户是否根据关键词检索
                if (null != blogIndexHit.highlight().get("title")) {
                    //获取高亮数据
                    String title = blogIndexHit.highlight().get("title").get(0);
                    blogIndexVo.setTitle(title);
                }
                //返回数据
                return blogIndexVo;
            }).collect(Collectors.toList());
            blogSearchResponseVo.setList(list);
        }
        return blogSearchResponseVo;
    }
}
